package com.gitee.wsl.reflect

import com.gitee.wsl.base.Option
import com.gitee.wsl.base.empty
import com.gitee.wsl.base.get
import com.gitee.wsl.base.ofNullable
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1

/**
 * An alias of CircularProperty
 */
fun <T> prop(value: T, observer: ((T) -> Unit)? = null) = CircularProperty(value, observer)

/**
 * Property delegate class
 * @param value value pass in to
 */
class CircularProperty<T>(var value: T, private var observer: ((T) -> Unit)? = null) :
    ReadWriteProperty<Any, T> {

   override fun getValue(thisRef: Any, property: KProperty<*>): T {
      return value
   }

   override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
      //thisRef.invalidate()
      val eq = this.value == value
      this.value = value
      if(!eq)
         observer?.invoke(this.value)
   }
}

class LazyVar<T>(val init:()->T): ReadWriteProperty<Any, T> {
   var cache: Option<T> = Option.empty()
   override fun getValue(thisRef: Any, property: KProperty<*>): T {
      if(cache.isNone())
         cache = Option.ofNullable(init())
      return cache.get()
   }

   override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
      cache = Option.ofNullable(value)
   }

}

fun<T> lazyInit(init:()->T): LazyVar<T> = LazyVar(init)
class LazyVal<T>(val init:(Option<T>)->Unit): ReadOnlyProperty<Any, T> {
   var cache: Option<T> = Option.empty()
   override fun getValue(thisRef: Any, property: KProperty<*>): T {
      if(cache.isNone())
         init(cache)
      return cache.get()
   }
}

fun<T> lazyValInit(init:(Option<T>)->Unit): LazyVal<T> = LazyVal(init)


open class Property<E,T>(val obj: E, val property: KProperty1<E, T?>, private val constraintViolations:MutableList<ConstraintViolation>) {

   /**
    * Validates the property by passing the constraint and the validation function
    *
    * This function is used by all constraint validations
    *
    * @param constraint specifies the function that returns the constraint to be validated
    * @param isValid specifies the validation function
    * @return the property validator
    */
   fun validate(constraint: (T?) -> Constraint, isValid: (T?) -> Boolean): Property<E,T> {
      val value = this.property.get(this.obj)
      if (!isValid(value)) {
         constraintViolations += DefaultConstraintViolation(
            property = this.property.name,
            value = value,
            constraint = constraint(value)
         )
      }
      return this
   }
}

interface ConstraintViolation {
   val property: String
   val value: Any?
   val constraint: Constraint
}

data class DefaultConstraintViolation(
   override val property: String,
   override val value: Any? = null,
   override val constraint: Constraint
) : ConstraintViolation

interface Constraint {
   val message: String
}